package com.hillky.channelhandler.handler;

import com.hillky.ServiceConfig;
import com.hillky.YrpcBootstrap;
import com.hillky.core.ShutDownHandler;
import com.hillky.enumeration.RequestType;
import com.hillky.enumeration.RespCode;
import com.hillky.protection.RateLimiter;
import com.hillky.protection.TokenBuketRateLimiter;
import com.hillky.transport.message.RequestPayload;
import com.hillky.transport.message.YrpcRequest;
import com.hillky.transport.message.YrpcResponse;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;

@Slf4j
public class MethodCallHandler extends SimpleChannelInboundHandler<YrpcRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, YrpcRequest yrpcRequest) throws Exception {
        // 1.先封装部分响应
        YrpcResponse yrpcResponse = new YrpcResponse();
        yrpcResponse.setRequestId(yrpcRequest.getRequestId());
        yrpcResponse.setCompressType(yrpcRequest.getCompressType());
        yrpcResponse.setSerializeType(yrpcRequest.getSerializeType());

        // 2.获得通道
        Channel channel = channelHandlerContext.channel();

        // 3.查看挡板是否打开，如果挡板已经打开，返回一个错误的响应
        if(ShutDownHandler.BAFFLE.get()==true){
            yrpcResponse.setCode(RespCode.CLOSING.getCode());
            channel.writeAndFlush(yrpcResponse);
            return;
        }

        // 4.处理前计数器加1
        ShutDownHandler.REQUEST_COUNTER.increment();

        // 5. 处理请求的逻辑
        // 我们如果要针对性的限流，应该一个地址匹配一个限流器
        SocketAddress socketAddress = channel.remoteAddress();
        Map<SocketAddress, RateLimiter> everyIpRateLimiter =
                YrpcBootstrap.getInstance().getConfiguration().getEveryIpRateLimiter();

        RateLimiter rateLimiter = everyIpRateLimiter.get(socketAddress);
        if(rateLimiter==null){
            rateLimiter=new TokenBuketRateLimiter(10,10);
            everyIpRateLimiter.put(socketAddress,rateLimiter);
        }
        boolean allowRequest = rateLimiter.allowRequest();
        // 限流
        if(!allowRequest){
            // 需要封装响应并且返回了
            yrpcResponse.setCode(RespCode.RATE_LIMIT.getCode());
            // 处理心跳
        }else if(yrpcRequest.getRequestType()== RequestType.HEART_BEAT.getId()){
            // 需要封装响应并返回
            yrpcResponse.setCode(RespCode.SUCCESS_HEART_BEAT.getCode());
        }else {
            // 正常调用
            /**----------------具体调用过程-----------------**/

            try{
                // （1）. 获取负载内容
                RequestPayload requestPayload = yrpcRequest.getRequestPayload();
                if (log.isDebugEnabled()) {
                    log.debug("请求【{}】已经在服务端完成方法调用", yrpcRequest.getRequestId());
                }
                // （2）.根据负载内容进行方法调用
                Object object = callTargetMethod(requestPayload);

                // （3）.封装响应 我们是否需要考虑另外一个问题，响应码，响应类型
                yrpcResponse.setCode(RespCode.SUCCESS.getCode());
                yrpcResponse.setBody(object);

            }catch (Exception e){
                log.error("请求编号为【{}】的请求在调用过程中发生异常",yrpcRequest.getRequestId(),e);
                yrpcResponse.setCode(RespCode.FAIL.getCode());
            }

        }
        // 6.写出响应
        channel.writeAndFlush(yrpcResponse);

        // 7.计数器减1
        ShutDownHandler.REQUEST_COUNTER.decrement();

    }

    private Object callTargetMethod(RequestPayload requestPayload) {

        String interfaceName = requestPayload.getInterfaceName();
        String methodName = requestPayload.getMethodName();
        Class<?>[] parametersType = requestPayload.getParametersType();
        Object[] parametersValue = requestPayload.getParametersValue();

        // 寻找合适的类完成方法调用
        ServiceConfig<?> serviceConfig = YrpcBootstrap.SERVERS_LIST.get(interfaceName);
        Object refImpl = serviceConfig.getRef();

        // 通过反射调用 1.获取方法对象  2.执行invoke方法

        Class<?> refImplClass = refImpl.getClass();

        Object invoke;
        try {
            Method method = refImplClass.getMethod(methodName, parametersType);
            invoke=method.invoke(refImpl, parametersValue);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            log.error("调用服务【{}】的方法【{}】时发生了异常",interfaceName,methodName,e);
            throw new RuntimeException(e);
        }

        return invoke;

    }
}
